package engine.exitcriteria;

import java.util.Iterator;
import java.util.List;
import java.util.logging.Logger;

import engine.ObjectiveFunction;
import engine.Population;
import engine.TerminationCondition;

/**
 * Checks if all individuals are the same.
 * 
 * @author test
 * 
 * @param <T>
 */
public class AllIndividualsAreSimilar<T> implements TerminationCondition<T> {
	
	private static final Logger LOGGER = Logger
			.getLogger(AllIndividualsAreSimilar.class.getName());
	
	private ObjectiveFunction<T> objFun;

	private double stdDevLevel = 1e-6;
	
	
	public AllIndividualsAreSimilar(ObjectiveFunction<T> objFun) {
		this.objFun = objFun;
	}

	public AllIndividualsAreSimilar(ObjectiveFunction<T> objFun, double stdDevLevel) {
		this(objFun);
		this.stdDevLevel = stdDevLevel;
	}
	
	@Override
	public boolean isSatisfied(Population<T> population) {
		List<T> individuals = population.getIndividuals();

		boolean terminate = areAllIndividualsTheSame(individuals);
		
		// if not the same check if stdDev is too low
		terminate |= isStdDevTooLow(individuals);

		if (terminate) {
			LOGGER
					.info("Terminating: All individuals are the same or stdDev is below "
							+ stdDevLevel);
		}
		
		return terminate;
	}

	private boolean areAllIndividualsTheSame(List<T> individuals) {
		// Yes, all the same for default.
		boolean result = true;

		// Check if there are at least two individuals in the population
		Iterator<T> iterator = individuals.iterator();
		if (iterator.hasNext()) {
			T ind1 = iterator.next();

			// iterate over the next elements ...
			for (; iterator.hasNext() && result;) {
				T ind = iterator.next();

				// ... and check if they are all equal to the first one
				if (!ind1.equals(ind)) {
					result = false;
				}
			}
		}
		return result;
	}

	private boolean isStdDevTooLow(List<T> inds) {
		return stdDev(inds) < stdDevLevel;
	}


	private double stdDev(List<T> inds) {
		double result = 0;
		double size = inds.size();
		double mean = mean(inds);
		
		for (T ind : inds) {
			double diff = objFun.compute(ind) - mean;
			result += (diff * diff);
		}
		result /= size;
		
		return Math.sqrt(result);
	}

	private double mean(List<T> inds) {
		double result = 0;
		double size = inds.size();
		
		for (T ind : inds) {
			result += objFun.compute(ind);
		}
		result /= size;
		
		return result;
	}
}
